/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       vector4.h
 * Author:     karooolek
 * Created on: 2009-01-24
 *
 **********************************************************************************************************************/

#ifndef VECTOR4_H_
#define VECTOR4_H_

#include "vector3.h"

namespace mGameEngine
{

/**
 * Four floats vector.
 * Components can be accessed by name or by index.
 */
struct Vector4
{
    union
    {
        /**
         * Vector components.
         */
        float v[4];

        /**
         * X, Y, Z, W vector coordinates.
         */
        struct
        {
            float x, y, z, w;
        };
    };

    /**
     * Create vector with initial values.
     * @param x X coordinate
     * @param y Y coordinate
     * @param z Y coordinate
     * @param w W coordinate
     */
    Vector4(float x = 0.0f, float y = 0.0f, float z = 0.0f, float w = 1.0f) :
        x(x), y(y), z(z), w(w)
    {
    }

    /**
     * Create vector with initial values.
     * @param v floats array
     */
    Vector4(const float *v) :
        x(v[0]), y(v[1]), z(v[2]), w(v[3])
    {
    }

    /**
     * Extend two dimensional vector.
     * @param v two dimensional vector
     * @param z extending Z coordinate
     * @param w extending W coordinate
     */
    Vector4(const Vector2 &v, float z = 0.0f, float w = 1.0f) :
        x(v.x), y(v.y), z(z), w(w)
    {
    }

    /**
     * Extend three dimensional vector.
     * @param v two dimensional vector
     * @param w extending W coordinate
     */
    Vector4(const Vector3 &v, float w = 1.0f) :
        x(v.x), y(v.y), z(v.z), w(w)
    {
    }

    /**
     * Copy constructor.
     * @param v vector to copy
     */
    Vector4(const Vector4 &v) :
        x(v.x), y(v.y), z(v.z), w(v.w)
    {
    }

    /**
     * Assign vector.
     * @param v vector to assign.
     * @return vector with assigned value.
     */
    Vector4 &operator=(const Vector2 &v)
    {
        x = v.x;    y = v.y;    z = 0.0f;    w = 1.0f;
        return *this;
    }

    /**
     * Assign vector.
     * @param v vector to assign.
     * @return vector with assigned value.
     */
    Vector4 &operator=(const Vector3 &v)
    {
        x = v.x;    y = v.y;    z = v.z;    w = 1.0f;
        return *this;
    }

    /**
     * Assign vector.
     * @param v vector to assign.
     * @return vector with assigned value.
     */
    Vector4 &operator=(const Vector4 &v)
    {
        x = v.x;    y = v.y;    z = v.z;    w = v.w;
        return *this;
    }

    /**
     * Add vector.
     * @param v vector to add
     * @return increased vector
     */
    Vector4 &operator+=(const Vector4 &v)
    {
        float wvw = w/v.w;
        x += v.x * wvw;    y += v.y * wvw;    z += v.z * wvw;
        return *this;
    }

    /**
     * Add vectors.
     * @param v vector to add
     * @return sum of vectors
     */
    Vector4 operator+(const Vector4 &v) const
    {
        float wvw = w/v.w;
        return Vector4(x+v.x*wvw, y+v.y*wvw, z+v.z*wvw, w);
    }

    /**
     * Negate vector.
     * @return negated vector
     */
    Vector4 operator-() const
    {
        return *this * -1.0f;
    }

    /**
     * Decrease vector.
     * @param v vector to subtract
     * @return decreased vector
     */
    Vector4 &operator-=(const Vector4 &v)
    {
        float wvw = w/v.w;
        x -= v.x * wvw;    y -= v.y * wvw;    z -= v.z * wvw;
        return *this;
    }

    /**
     * Subtract vectors.
     * @param v vector to subtract
     * @return difference of vectors
     */
    Vector4 operator-(const Vector4 &v) const
    {
        float wvw = w/v.w;
        return Vector4(x-v.x * wvw, y-v.y * wvw, z-v.z * wvw, w);
    }

    /**
     * Multiply by scalar.
     * @param a scalar multiplier
     * @return multiplied vector
     */
    Vector4 &operator*=(float a)
    {
        x *= a;    y *= a;    z *= a;
        return *this;
    }

    /**
     * Multiply vector by scalar.
     * @param a scalar multiplier
     * @return multiplied vectors
     */
    Vector4 operator*(float a) const
    {
        return Vector4(x*a, y*a, z*a, w);
    }

    /**
     * Multiply scalar by vector.
     * @param a scalar
     * @param v vector
     * @return multiplied vector and scalar
     */
    friend Vector4 operator*(float a, const Vector4 &v)
    {
        return v * a;
    }

    /**
     * Multiply by vector (component-wise).
     * @param v multiplier
     * @return multiplied vector
     */
    Vector4 &operator*=(const Vector4 &v)
    {
        x *= v.x;    y *= v.y;    z *= v.z;    w *= v.w;
        return *this;
    }

    /**
     * Multiply vectors (component-wise).
     * @param v multiplier
     * @return multiplied vectors
     */
    Vector4 operator*(const Vector4 &v) const
    {
        return Vector4(x*v.x, y*v.y, z*v.z, w*v.w);
    }


    /**
     * Divide by scalar.
     * @param a scalar divisor
     * @return divided vector
     */
    Vector4 &operator/=(float a)
    {
        x /= a;    y /= a;    z /= a;
        return *this;
    }

    /**
     * Divide by scalar.
     * @param a scalar divisor
     * @return divided vector
     */
    Vector4 operator/(float a) const
    {
        return Vector4(x/a, y/a, z/a, w);
    }

    /**
     * Divide by vector (component-wise).
     * @param v divisor
     * @return divided vector
     */
    Vector4 &operator/=(const Vector4 &v)
    {
        x /= v.x;    y /= v.y;    z /= v.z;    w /= v.w;
        return *this;
    }

    /**
     * Divide vectors (component-wise).
     * @param v divisor
     * @return divided vectors
     */
    Vector4 operator/(const Vector4 &v) const
    {
        return Vector4(x/v.x, y/v.y, z/v.z, w/v.w);
    }

    /**
     * Vectors equal.
     * @param v vector to compare
     * @return true if all components equal
     */
    bool operator==(const Vector4 &v) const
    {
        return x == v.x && y == v.y && z == v.z && w == v.w;
    }

    /**
     * Vectors differ.
     * @param v vector to compare
     * @return true if any off components differs
     */
    bool operator!=(const Vector4 &v) const
    {
        return x != v.x || y != v.y || z != v.z || w != v.w;
    }

    /**
     * Two dimensional vector cast.
     * @return two dimensional vector.
     */
    operator Vector2()
    {
        return Vector2(x, y);
    }

    /**
     * Three dimensional vector cast.
     * @return three dimensional vector.
     */
    operator Vector3()
    {
        return Vector3(x, y, z);
    }

    /**
     * Floats array cast.
     * @return floats array
     */
    operator float *()
    {
        return v;
    }

    /**
     * Floats const array cast.
     * @return floats const array
     */
    operator const float *() const
    {
        return v;
    }

    /**
     * Normalize vector.
     * @return normalized vector
     */
    Vector4 &normalize()
    {
        float l = length(*this);
        return l ? *this /= l : *this;
    }

    /**
     * Calculate dot product of two vectors.
     * @param v1 first vector
     * @param v2 second vector
     * @return dot product of two vectors
     */
    friend float dot(const Vector4 &v1, const Vector4 &v2)
    {
        return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z) / (v1.w * v2.w);
    }

    /**
     * Calculate cross product of two vectors.
     * @param v1 first vector
     * @param v2 second vector
     * @return cross product of two vectors
     */
    friend Vector4 cross(const Vector4 &v1, const Vector4 &v2)
    {
        return Vector4(    v1.y * v2.z - v1.z * v2.y,
                        v1.z * v2.x - v1.x * v2.z,
                        v1.x * v2.y - v1.y * v2.x,
                        1.0f) / (v1.w * v2.w);
    }

    /**
     * Calculate length of three dimensional vector.
     * @param v vector.
     * @return vectors length.
     */
    friend float length(const Vector4 &v)
    {
        return sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z)) / v.w;
    }

    /**
     * Spherical linear interpolation between two vectors.
     * @param v1 first vector
     * @param v2 second vector
     * @param t interpolation factor.
     * @return spherically linearly interpolated vector between v1 and v2.
     */
    friend Vector4 slerp(const Vector4 &v1, const Vector4 &v2, float t)
    {
        return Vector4(slerp(Vector3(v1), Vector3(v2), t), lerp(v1.w, v2.w, t));
    }
};

}

#endif // VECTOR4_H_
